package se.topshelf.golftracker.core.domain;

import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.text.MessageFormat;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 *  Describes the result of played golf hole. Contains references to the individual shots made on the hole.
 */
@Entity
@Table(name = "HoleResult")
public class HoleResult extends BaseEntity implements Comparable<HoleResult>{
    @NotNull
    @ManyToOne
    @JoinColumn(name = "holeId")
    private Hole hole;

    @NotNull
    @ManyToOne
    @JoinColumn(name = "roundId")
    private Round round;

    private int score;

    @OneToMany(mappedBy = "holeResult", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    @Sort(type = SortType.NATURAL)
    private SortedSet<Shot> shots = new TreeSet<Shot>();

    private String description;

    public HoleResult() {}

    public HoleResult(Hole hole, Round round) {
        this.hole = hole;
        this.round = round;
    }

    public Hole getHole() {
        return hole;
    }

    public void setHole(Hole hole) {
        this.hole = hole;
    }

    public Round getRound() {
        return round;
    }

    public void setRound(Round round) {
        this.round = round;
    }

    public int getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

    /**
     * @Todo Test
     * @return  The net score of the. E.g. if scored 4 shots, and par is 4, 0 is returned.
     * If scored 3 and par is 4, -1 is returned.
     */
    public Integer getNetScore(){
        Integer par = getHole().getPar();
        return score- par;
    }

    public SortedSet<Shot> getShots() {
        return shots;
    }

    public void addShot(Shot shot){
        // No dupe check is needed here because the order is decided internally and also sets uniqueness
        shot.setShotOrder(shots.size());
        shot.setHoleResult(this);
        shots.add(shot);
        score++;
    }
    public void removeShot(Shot shot){
        if(!shots.contains(shot)){
            throw new IllegalArgumentException(MessageFormat.format("Cannot remove shot that does not exist on HoleResult: {0}", shot));
        }
        shots.remove(shot);
        shot.setHoleResult(null);
        score--;
    }

    // Use add
    public void setShots(SortedSet<Shot> shots) {
        this.shots = shots;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public int compareTo(HoleResult o) {
        int roundResult = round.compareTo(o.getRound());
        if(roundResult == 0){
            return hole.compareTo(o.getHole());
        }
        return 0;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        HoleResult that = (HoleResult) o;

        if (!hole.equals(that.hole)) return false;
        if (!round.equals(that.round)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = hole.hashCode();
        result = 31 * result + round.hashCode();
        return result;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("HoleResult");
        sb.append("{hole=").append(hole);
        sb.append(", round=").append(round);
        sb.append(", score=").append(score);
        sb.append('}');
        return sb.toString();
    }
}
